package com.general.client.widgets.iterablelist;

import java.util.ArrayList;
import java.util.Iterator;

import com.general.client.action.ActionHandler;
import com.general.client.basewidget.BaseWidget;
import com.general.client.callback.Callback;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.Widget;

public class IterableList extends BaseWidget implements IIterableList,
		Focusable {

	private static IterableListUiBinder uiBinder = GWT
			.create(IterableListUiBinder.class);

	interface IterableListUiBinder extends UiBinder<Widget, IterableList> {
	}

	ArrayList<Widget> widgets = new ArrayList<Widget>();

	@UiField
	FocusPanel topFocus;

	@UiField
	FlowPanel widgetsContainer;

	private Callback<Integer> selectionChangedCallback;

	private Callback<Integer> highlightedChangedCallback;

	private int selectedIndex = -1;

	private int highlightedIndex = -1;

	private ActionHandler itemActionHandler;

	private BlurHandler blurHandler;

	private final ArrayList<String> highlightStyles = new ArrayList<String>();

	private final ArrayList<String> selectedStyles = new ArrayList<String>();

	private final ArrayList<String> idleStyles = new ArrayList<String>();

	private Timer blurGraceTimer;

	private final int BLUR_GRACE_TIME = 200; // ms

	private enum ADDREMOVE {
		ADD, REMOVE;
	}

	public IterableList() {
		initWidget(uiBinder.createAndBindUi(this));

		// add grace time before blur so when i click on an elem in the list
		// the topFocus will not lose focus
		topFocus.addFocusHandler(new FocusHandler() {

			@Override
			public void onFocus(FocusEvent event) {
				if (blurGraceTimer != null) {
					blurGraceTimer.cancel();
					blurGraceTimer = null;
				}
			}
		});
		topFocus.addBlurHandler(new BlurHandler() {

			@Override
			public void onBlur(final BlurEvent event) {
				// setFocus(true);
				blurGraceTimer = new Timer() {

					@Override
					public void run() {
						setFocus(false);
						if (blurHandler != null)
							blurHandler.onBlur(event);
					}
				};
				blurGraceTimer.schedule(BLUR_GRACE_TIME);
			}
		});

		topFocus.addKeyDownHandler(new KeyDownHandler() {

			@Override
			public void onKeyDown(KeyDownEvent event) {
				// handle key ENTER to select elem
				// (only if highlighted elem is not -1,
				// if -1 do nothing)
				if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
					if (highlightedIndex != -1) {
						setSelectedIndex(highlightedIndex);
						if (itemActionHandler != null)
							itemActionHandler.onAction(event);
					}
				}

				// handle key ESC to highlight non (-1)
				if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
					setHighlightedIndex(-1);
					setFocus(false);
				}

				// handle key UP to highlight elem above current
				// if current highlighted elem is -1 than highlight last
				// elem in the list
				if (event.getNativeKeyCode() == KeyCodes.KEY_UP) {
					if (widgets.isEmpty())
						return;
					if (highlightedIndex == -1 || highlightedIndex - 1 == -1) {
						setHighlightedIndex(widgets.size() - 1);
					} else {
						setHighlightedIndex(highlightedIndex - 1);
					}
				}
				// handle key DOWN to highlight elem below current
				// if current highlighted elem is last in list than highlight
				// first elem in the list
				if (event.getNativeKeyCode() == KeyCodes.KEY_DOWN) {
					if (widgets.isEmpty())
						return;
					if (highlightedIndex == -1
							|| highlightedIndex + 1 == widgets.size()) {
						setHighlightedIndex(0);
					} else {
						setHighlightedIndex(highlightedIndex + 1);
					}
				}

			}
		});
	}

	@Override
	public void add(Widget w) {
		widgets.add(w);
		FocusPanel fp = new FocusPanel();
		fp.add(w);
		addHandlersToWidgetWrapper(fp, widgets.indexOf(w));

		widgetsContainer.add(fp);
		changeStyle(widgets.indexOf(w), ADDREMOVE.ADD, idleStyles);
	}

	private void addHandlersToWidgetWrapper(FocusPanel fp, final int indexOf) {
		// clicking should select elem.
		fp.addClickHandler(new ClickHandler() {

			@Override
			public void onClick(ClickEvent event) {
				setSelectedIndex(indexOf);
				if (itemActionHandler != null)
					itemActionHandler.onAction(event);
				// return focus to topFocus
				setFocus(true);
			}
		});
		// hovering should highlight the elem.
		fp.addMouseOverHandler(new MouseOverHandler() {

			@Override
			public void onMouseOver(MouseOverEvent event) {
				setHighlightedIndex(indexOf);
			}
		});
		// internal elems. in the list should not gain focus. ever.
		fp.addFocusHandler(new FocusHandler() {

			@Override
			public void onFocus(FocusEvent event) {
				setFocus(true);
			}
		});
	}

	@Override
	public void clear() {
		widgets.clear();
		widgetsContainer.clear();
	}

	@Override
	public Iterator<Widget> iterator() {
		return widgets.iterator();
	}

	@Override
	public boolean remove(Widget w) {
		int indexOf = widgets.indexOf(w);
		if (indexOf == -1)
			return false;
		widgets.remove(indexOf);
		widgetsContainer.remove(indexOf);
		return true;
	}

	@Override
	/** 
	 * callback with the index of new selection.
	 * -1 if new selection is null\not in list
	 * @param callback
	 */
	public void onSelectionChanged(Callback<Integer> callback) {
		this.selectionChangedCallback = callback;
	}

	@Override
	/**
	 * callback with the index of new highlighted elem.
	 * -1 if new highlighted elem is null\not in list
	 */
	public void onHighlightedChanged(Callback<Integer> callback) {
		this.highlightedChangedCallback = callback;
	}

	@Override
	/** 
	 * @return index of selected elem., -1 if non selected 
	 */
	public int getSelectedIndex() {
		return selectedIndex;
	}

	@Override
	/**
	 * @return index of highlighted elem., -1 if non highlighted
	 */
	public int getHighlightedIndex() {
		return highlightedIndex;
	}

	@Override
	/**
	 * @param index of new selection., -1 to select none
	 * values outside the list will be ignored (except -1 ...)
	 */
	public void setSelectedIndex(int index) {
		if (index < -1 || widgets.size() <= index)
			return;
		// remove old selection style
		if (selectedIndex != -1) {
			changeStyle(selectedIndex, ADDREMOVE.REMOVE, selectedStyles);
		}
		selectedIndex = index;
		// set new selection style
		if (index != -1) {
			changeStyle(index, ADDREMOVE.ADD, selectedStyles);
		}
		// call callback
		if (selectionChangedCallback != null)
			selectionChangedCallback.onItemChanged(index);
	}

	private void changeStyle(int index, ADDREMOVE addremove,
			ArrayList<String> styles) {
		Widget w = widgetsContainer.getWidget(index);
		for (String style : styles) {
			if (addremove.equals(ADDREMOVE.ADD)) {
				w.addStyleName(style);
			} else if (addremove.equals(ADDREMOVE.REMOVE)) {
				w.removeStyleName(style);
			}
		}
		// // add\remove idle style name
		// for (String style : idleStyles) {
		// if (addremove.equals(ADDREMOVE.ADD)) {
		// w.removeStyleName(style);
		// } else if (addremove.equals(ADDREMOVE.REMOVE)) {
		// // && index != selectedIndex && index != highlightedIndex
		// w.addStyleName(style);
		// }
		// }
	}

	@Override
	/**
	 * @param index of new highlighted elem., -1 to highlight none
	 * values outside the list will be ignored (except -1 ...)
	 */
	public void setHighlightedIndex(int index) {
		if (index < -1 || widgets.size() <= index)
			return;
		// remove old highlighted style
		if (highlightedIndex != -1) {
			changeStyle(highlightedIndex, ADDREMOVE.REMOVE, highlightStyles);
		}
		highlightedIndex = index;
		// set new highlighted style
		if (index != -1) {
			changeStyle(index, ADDREMOVE.ADD, highlightStyles);
		}
		// call callback
		if (highlightedChangedCallback != null)
			highlightedChangedCallback.onItemChanged(index);
	}

	@Override
	/**
	 * @param action for items (click\enter key)
	 */
	public HandlerRegistration addActionHandler(ActionHandler action) {
		this.itemActionHandler = action;
		return new HandlerRegistration() {

			@Override
			public void removeHandler() {
				itemActionHandler = null;
			}
		};
	}

	@Override
	/**
	 * @param blurHandler action for THE LIST losing focus
	 */
	public HandlerRegistration addBlurHandler(BlurHandler blurHandler1) {
		this.blurHandler = blurHandler1;
		return new HandlerRegistration() {

			@Override
			public void removeHandler() {
				blurHandler = null;
			}
		};
	}

	@Override
	/**
	 * @param styleName name for items when highlighted
	 */
	public void addHighlightStyleName(String styleName) {
		highlightStyles.add(styleName);
	}

	@Override
	/**
	 * @param styleName name for items when selected
	 */
	public void addSelectionStyleName(String styleName) {
		selectedStyles.add(styleName);
	}

	@Override
	/**
	 * @param styleName name for items when idle
	 */
	public void addIdleStyleName(String styleName) {
		idleStyles.add(styleName);
	}

	/*
	 * START OF Focusable delegators
	 */
	@Override
	public int getTabIndex() {
		return topFocus.getTabIndex();
	}

	@Override
	public void setAccessKey(char key) {
		topFocus.setAccessKey(key);
	}

	@Override
	public void setFocus(boolean focused) {
		topFocus.setFocus(focused);
	}

	@Override
	public void setTabIndex(int index) {
		topFocus.setTabIndex(index);
	}

	/*
	 * END OF Focusable delegators
	 */

	@Override
	public Widget asWidget() {
		return topFocus;
	}
}
